#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using PHMEE.CommonUtils;
using PHMEE.Diagnosis;
using PHMEE.DTOS.Diagnosis;
using PHMEE.EnergyEfficiency;
using PHMEE.Entities.DaqInfo.Ams;
using PHMEE.Entities.Diagnosis;
using PHMEE.IServices.Diagnosis;
using PHMEE.Localization;
using PHMEE.Managers.Diagnosis;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;

namespace PHMEE.Services.Diagnosis;

public class AlarmRecordAppService : PHMEEAppService, IAlarmRecordAppService
{
    private readonly IRepository<AlarmRecord, Guid> _alarmRecordRepository;
    private readonly IReadOnlyRepository<Point, Guid> _pointRepository;
    private readonly IReadOnlyRepository<Unit, Guid> _unitRepository;
    private readonly IReadOnlyRepository<Equipment, Guid> _equipmentRepository;
    private readonly IMainenanceAdviceRepository _maintenanceAdviceRepository;
    private readonly IStringLocalizer<DiagnosisResource> _localizer;
    // private readonly IAlarmRecordAckedEventPublisher _alarmRecordAckedEventPublisher;
    private readonly IStringLocalizer<EquipParametersResource> _equipParametersLocalizer;
    private readonly IVesselRepository _vesselRepository;
    private readonly IPHMEETimeProvider _phmeeTimeProvider;
    private readonly IReadOnlyRepository<DigitalConfig, Guid> _digitalConfigRepository;
    private readonly IDigitalTDRepository _digitalTDRepository;

    public AlarmRecordAppService(IRepository<AlarmRecord, Guid> alarmRecordRepository,
        IReadOnlyRepository<Point, Guid> pointRepository,
        IReadOnlyRepository<Unit, Guid> unitRepository,
        IReadOnlyRepository<Equipment, Guid> equipmentRepository,
        IMainenanceAdviceRepository maintenanceAdviceRepository,
        IStringLocalizer<DiagnosisResource> localizer,
        // IAlarmRecordAckedEventPublisher alarmRecordAckedEventPublisher,
        IStringLocalizer<EquipParametersResource> equipParametersLocalizer,
        IVesselRepository vesselRepository,
        IPHMEETimeProvider phmeeTimeProvider,
        IReadOnlyRepository<DigitalConfig, Guid> digitalConfigRepository,
        IDigitalTDRepository digitalTDRepository)
    {
        _alarmRecordRepository = alarmRecordRepository;
        _pointRepository = pointRepository;
        _unitRepository = unitRepository;
        _equipmentRepository = equipmentRepository;
        _maintenanceAdviceRepository = maintenanceAdviceRepository;
        _localizer = localizer;
        // _alarmRecordAckedEventPublisher = alarmRecordAckedEventPublisher;
        _equipParametersLocalizer = equipParametersLocalizer;
        _vesselRepository = vesselRepository;
        _phmeeTimeProvider = phmeeTimeProvider;
        _digitalConfigRepository = digitalConfigRepository;
        _digitalTDRepository = digitalTDRepository;
    }

    public async Task<PagedResultDto<AlarmRecordBriefDto>> GetListAsync(Guid vesselId, AlarmRecordListFilterDto input)
    {
        var queryable = await _alarmRecordRepository.GetQueryableAsync();

        // 查询
        var query =
            from ar in queryable

            // Left Join with Point
            join point in await _pointRepository.GetQueryableAsync()
                on ar.PointId equals point.Id into pointGroup
            from point in pointGroup.DefaultIfEmpty()

            // Left Join with Unit
            join unit in await _unitRepository.GetQueryableAsync()
                on ar.UnitId equals unit.Id into unitGroup
            from unit in unitGroup.DefaultIfEmpty()

            // Left Join with Equipment
            join equip in await _equipmentRepository.GetQueryableAsync()
                on ar.EquipId equals equip.Id into equipGroup
            from equip in equipGroup.DefaultIfEmpty()

            // Where clause to filter by AlarmTimeUtc
            where ar.AlarmTimeUtc >= input.StartTimeUtc && ar.AlarmTimeUtc <= input.EndTimeUtc && ar.VesselId == vesselId
            select new
            {
                AlarmRecordBriefDto = new AlarmRecordBriefDto
                {
                    Id = ar.Id,
                    UnitName = equip != null
                        ? equip.DisplayName
                        : unit != null
                            ? unit.DisplayName
                            : null,
                    UnitId = ar.UnitId,
                    EquipId = ar.EquipId,
                    Area = unit != null ? unit.Area : null,
                    AlarmPointName = point != null ? point.DisplayName : null,
                    PointId = ar.PointId,
                    AlarmTimeUtc = ar.AlarmTimeUtc,
                },
                IsAcked = ar.IsAcked,
                FeatureAlarmLevels = ar.FeatureAlarmLevels,
            };

        // 获取总数
        var totalCount = await query.CountAsync();

        // 分页，暂定按报警时间倒序
        query = query.OrderByDescending(q => q.AlarmRecordBriefDto.AlarmTimeUtc)
            .Skip(input.SkipCount)
            .Take(input.MaxResultCount);

        // 执行查询
        var queryResult = await AsyncExecuter.ToListAsync(query);

        foreach (var res in queryResult)
        {
            // 获取报警级别最高的特征值 - 报警等级
            var maxAlarmLevel = res.FeatureAlarmLevels.MaxBy(f => (int)f.Value);
            res.AlarmRecordBriefDto.AlarmFeatureValue = maxAlarmLevel.Key.ToString();
            res.AlarmRecordBriefDto.AlarmLevel = maxAlarmLevel.Value.ToString();

            // IsAcked 文本国际化
            res.AlarmRecordBriefDto.IsAcked = res.IsAcked
                ? _localizer[DiagnosisParameterKeys.Yes]
                : _localizer[DiagnosisParameterKeys.No];
        }

        return new PagedResultDto<AlarmRecordBriefDto>(totalCount,
            queryResult.Select(q => q.AlarmRecordBriefDto).ToList());
    }

    public async Task<AlarmRecordDetailDto> GetDetailAsync(Guid id)
    {
        var alarmRecord = await _alarmRecordRepository.FirstOrDefaultAsync(ar => ar.Id == id);
        if (alarmRecord == null)
        {
            throw new UserFriendlyException(_localizer[DiagnosisErrorCodes.DiagAlarmRecordNotFound],
                DiagnosisErrorCodes.DiagAlarmRecordNotFound);
        }

        var point = await _pointRepository.FirstOrDefaultAsync(p => p.Id == alarmRecord.PointId);
        if (point == null)
        {
            throw new UserFriendlyException(_localizer[DiagnosisErrorCodes.DiagPointNotFound],
                DiagnosisErrorCodes.DiagPointNotFound);
        }

        var unit = await (await _unitRepository.GetQueryableAsync())
            .Where(u => u.Id == alarmRecord.UnitId)
            .Include(u => u.Equipments)
            .FirstOrDefaultAsync();
        if (unit == null)
        {
            throw new UserFriendlyException(_localizer[DiagnosisErrorCodes.DiagUnitNotFound],
                DiagnosisErrorCodes.DiagUnitNotFound);
        }

        Equipment? equipment = null;
        if (alarmRecord.EquipId is not null)
        {
            equipment = await _equipmentRepository.FirstOrDefaultAsync(e => e.Id == alarmRecord.EquipId);
            if (equipment == null)
            {
                throw new UserFriendlyException(_localizer[DiagnosisErrorCodes.DiagEquipmentNotFound],
                    DiagnosisErrorCodes.DiagEquipmentNotFound);
            }
        }

        var maintenanceAdvices = await _maintenanceAdviceRepository.GetListByFaultCodesAsync(alarmRecord.FaultCodes);

        var maintenanceInfos = maintenanceAdvices.Select(ma => new MaintenanceInfoDto
        {
            Type = ma.TypeDisplayName,
            Advice = ma.AdviceDisplayName
        }).ToArray();

        var featureAlarmLevels = alarmRecord.FeatureAlarmLevels
            .Select(f => new FeatureAlarmLevelsDto
            {
                FeatureValueName = f.Key.ToString(),
                AlarmLevel = f.Value.ToString()
            });

        return new AlarmRecordDetailDto
        {
            UnitName = unit.DisplayName,
            EquipName = equipment?.DisplayName ?? null,
            PointName = point.DisplayName,
            FeatureAlarmLevels = featureAlarmLevels,
            AlarmTimeUtc = alarmRecord.AlarmTimeUtc,
            MaintenanceInfos = maintenanceInfos
        };
    }

    public async Task<AlarmRecordAckStatusDto> GetAckStatusAsync(Guid id)
    {
        var queryable = await _alarmRecordRepository.GetQueryableAsync();

        // 查询
        var query =
            from ar in queryable

            // Left Join with Point
            join point in await _pointRepository.GetQueryableAsync()
                on ar.PointId equals point.Id into pointGroup
            from point in pointGroup.DefaultIfEmpty()

            // Left Join with Unit
            join unit in await _unitRepository.GetQueryableAsync()
                on ar.UnitId equals unit.Id into unitGroup
            from unit in unitGroup.DefaultIfEmpty()

            // Left Join with Equipment
            join equip in await _equipmentRepository.GetQueryableAsync()
                on ar.EquipId equals equip.Id into equipGroup
            from equip in equipGroup.DefaultIfEmpty()

            // Where clause to filter by AlarmTimeUtc
            where ar.Id == id

            // Select the required fields, handling potential null values
            select new AlarmRecordAckStatusDto
            {
                IsAcked = ar.IsAcked,
                UnitName = equip != null
                    ? equip.DisplayName
                    : unit != null
                        ? unit.DisplayName
                        : null,
                AlarmPointName = point != null ? point.DisplayName : null,
                AckedByUserName = ar.AckedByUserName,
                Remark = ar.Remark
            };

        var queryResult = await AsyncExecuter.FirstOrDefaultAsync(query);

        if (queryResult == null)
        {
            throw new UserFriendlyException(_localizer[DiagnosisErrorCodes.DiagAlarmRecordNotFound],
                DiagnosisErrorCodes.DiagAlarmRecordNotFound);
        }

        return queryResult;
    }

    public async Task SetAckStatusAsync(AlarmRecordAckStatusSetDto input)
    {
        var alarmRecord = await _alarmRecordRepository.FirstOrDefaultAsync(ar => ar.Id == input.Id);
        if (alarmRecord == null)
        {
            throw new UserFriendlyException(_localizer[DiagnosisErrorCodes.DiagAlarmRecordNotFound],
                DiagnosisErrorCodes.DiagAlarmRecordNotFound);
        }

        // // 如果原 Ack 状态为未确认，更新后为已确认，则发布事件
        // if (alarmRecord.IsAcked is false && input.IsAcked is true)
        // {
        //     await _alarmRecordAckedEventPublisher.PublishAlarmRecordAckedEventAsync(input.Id, alarmRecord.UnitId,
        //         alarmRecord.EquipId, input.IsAcked);
        // }

        alarmRecord.IsAcked = input.IsAcked;
        alarmRecord.Remark = input.Remark;
        alarmRecord.AckedById = CurrentUser?.Id ?? null;
        alarmRecord.AckedByUserName = CurrentUser?.UserName ?? null;

        await _alarmRecordRepository.UpdateAsync(alarmRecord);
    }

    // 获取报警诊断报告
    public async Task<AlarmDiagReportDto> GetAlarmDiagReportAsync(Guid id)
    {
        var alarmRecord = await _alarmRecordRepository.FirstOrDefaultAsync(ar => ar.Id == id);
        if (alarmRecord == null)
        {
            throw new UserFriendlyException(_localizer[DiagnosisErrorCodes.DiagAlarmRecordNotFound],
                DiagnosisErrorCodes.DiagAlarmRecordNotFound);
        }

        var point = await _pointRepository.FirstOrDefaultAsync(p => p.Id == alarmRecord.PointId);
        if (point == null)
        {
            throw new UserFriendlyException(_localizer[DiagnosisErrorCodes.DiagPointNotFound],
                DiagnosisErrorCodes.DiagPointNotFound);
        }

        var unit = await (await _unitRepository.GetQueryableAsync())
            .Where(u => u.Id == alarmRecord.UnitId)
            .Include(u => u.Equipments)
            .FirstOrDefaultAsync();
        if (unit == null)
        {
            throw new UserFriendlyException(_localizer[DiagnosisErrorCodes.DiagUnitNotFound],
                DiagnosisErrorCodes.DiagUnitNotFound);
        }

        Equipment? equipment = null;
        if (alarmRecord.EquipId is not null)
        {
            equipment = await _equipmentRepository.FirstOrDefaultAsync(e => e.Id == alarmRecord.EquipId);
            if (equipment == null)
            {
                throw new UserFriendlyException(_localizer[DiagnosisErrorCodes.DiagEquipmentNotFound],
                    DiagnosisErrorCodes.DiagEquipmentNotFound);
            }
        }

        // 组合返回值

        var maintenanceAdvices = await _maintenanceAdviceRepository.GetListByFaultCodesAsync(alarmRecord.FaultCodes);

        var maintenanceInfos = maintenanceAdvices.Select(ma => new MaintenanceInfoDto
        {
            Type = ma.TypeDisplayName,
            Advice = ma.AdviceDisplayName
        }).ToArray();

        var featureAlarmLevels = alarmRecord.FeatureAlarmLevels
            .Select(f => new FeatureAlarmLevelsDto
            {
                FeatureValueName = f.Key.ToString(),
                AlarmLevel = f.Value.ToString()
            })
            .ToList();

        // 报警信息
        AlarmInfoDto alarmInfo = new AlarmInfoDto
        {
            PointName = point.DisplayName,
            AlarmTimeUtc = alarmRecord.AlarmTimeUtc,
            FeatureAlarmLevels = featureAlarmLevels,
        };
        // 报警特征值
        IEnumerable<AlarmFeatureValue> alarmFeatureValues = alarmRecord.FeatureAlarmLevels.Keys;
        // 图谱分析时间范围，往前查 7 天
        DateTime spectralAnalysisEndTimeUtc = alarmRecord.AlarmTimeUtc;
        DateTime spectralAnalysisStartTimeUtc = spectralAnalysisEndTimeUtc.AddDays(-7);

        // 如果 equipment 不为空，则说明该测点关联到了设备，而不是机组
        if (equipment is not null)
        {
            return new AlarmDiagReportDto
            {
                PointId = alarmRecord.PointId,
                Unit = equipment.DisplayName,
                // Parameters = [GetEquipParametersFromEquipment(equipment)],
                AlarmInfo = alarmInfo,
                AlarmFeatureValues = alarmFeatureValues,
                MaintenanceInfos = maintenanceInfos,
                SpectralAnalysisStartTimeUtc = spectralAnalysisStartTimeUtc,
                SpectralAnalysisEndTimeUtc = spectralAnalysisEndTimeUtc
            };
        }

        return new AlarmDiagReportDto
        {
            PointId = alarmRecord.PointId,
            Unit = unit.DisplayName,
            // Parameters = GetEquipParametersFromUnit(unit),
            AlarmInfo = alarmInfo,
            AlarmFeatureValues = alarmFeatureValues,
            MaintenanceInfos = maintenanceInfos,
            SpectralAnalysisStartTimeUtc = spectralAnalysisStartTimeUtc,
            SpectralAnalysisEndTimeUtc = spectralAnalysisEndTimeUtc
        };
    }

    /// <summary>
    /// 获取指定机组的最新未被确认的报警记录集合（5 条），不含诊断结果为 no_fault 的记录
    /// </summary>
    /// <param name="vesselId"></param>
    /// <param name="unitId"></param>
    /// <returns></returns>
    public async Task<IEnumerable<AlarmRecordRealtimeDto>> GetLatestUnAckedAlarmRecordsAsync(Guid vesselId, Guid unitId)
    {
        var queryable = await _alarmRecordRepository.GetQueryableAsync();

        // 查询
        var query =
            from ar in queryable

            // Left Join with Point
            join point in await _pointRepository.GetQueryableAsync()
                on ar.PointId equals point.Id into pointGroup
            from point in pointGroup.DefaultIfEmpty()

            // Where clause to filter by UnitId and IsAcked
            where ar.UnitId == unitId && ar.IsAcked == false && ar.IsNoFault == false && ar.VesselId == vesselId

            // Select the required fields, handling potential null values
            select new
            {
                AlarmRecordId = ar.Id,
                AlarmTimeUtc = ar.AlarmTimeUtc,
                PointId = ar.PointId,
                PointDisplayName = point.DisplayName,
                UnitId = ar.UnitId,
                EquipId = ar.EquipId,
                FeatureAlarmLevels = ar.FeatureAlarmLevels,
                FaultCodes = ar.FaultCodes
            };

        // 分页，暂定按报警时间倒序
        query = query.OrderByDescending(q => q.AlarmTimeUtc)
            .Take(5);

        var queryResult = await AsyncExecuter.ToListAsync(query);

        // 故障码
        var allFaultCodes = queryResult.SelectMany(q => q.FaultCodes).Distinct();

        // 维保建议
        var maintenanceAdvices = await _maintenanceAdviceRepository.GetListByFaultCodesAsync(allFaultCodes);

        List<AlarmRecordRealtimeDto> alarmRecordRealtimeDtos = [];

        foreach (var item in queryResult)
        {
            // 特征值-报警等级
            var featureAlarmLevels = item.FeatureAlarmLevels
                .Select(f => new FeatureAlarmLevelsDto
                {
                    FeatureValueName = f.Key.ToString(),
                    AlarmLevel = f.Value.ToString()
                });
            // 维保建议
            var maintenanceInfos = maintenanceAdvices
                .Where(ma => item.FaultCodes.Contains(ma.Code))
                .Select(ma => new MaintenanceInfoDto
                {
                    Type = ma.TypeDisplayName,
                    Advice = ma.AdviceDisplayName
                });

            alarmRecordRealtimeDtos.Add(new AlarmRecordRealtimeDto
            {
                AlarmRecordId = item.AlarmRecordId,
                AlarmRecordTimeUtc = item.AlarmTimeUtc,
                PointId = item.PointId,
                PointName = item.PointDisplayName,
                UnitId = item.UnitId,
                EquipId = item.EquipId,
                FeatureAlarmLevels = featureAlarmLevels,
                MaintenanceInfos = maintenanceInfos
            });
        }

        return alarmRecordRealtimeDtos;
    }

    /// <summary>
    /// 获取船舶所有机组的振动未被 Ack 的报警 + Ams 开关量报警的数量，并按机组分组返回
    /// </summary>
    /// <param name="vesselId"></param>
    /// <returns></returns>
    public async Task<List<RealTimeUnitsAlarmCountDto>> GetRealTimeUnitsAlarmCountAsync(Guid vesselId)
    {
        var vessel = await _vesselRepository.PhmeeGetById(vesselId);

        if (vessel is null)
        {
            return [];
        }

        // 获取机组 Id 列表，并按照 Type 和 Number 排序
        var units = vessel.Units.OrderBy(item => item.Type).ThenBy(item => item.Number).ToList();
        
        var unitIds = units.Select(u => u.Id).ToList();

        var queryable = await _alarmRecordRepository.GetQueryableAsync();

        // 查询
        var query =
            from ar in queryable

            // Where clause to filter by UnitId and IsAcked
            where unitIds.Contains(ar.UnitId) && ar.IsAcked == false

            // Select the required fields and group by UnitId to count directly in the query
            group ar by ar.UnitId
            into unitGroup
            select new
            {
                UnitId = unitGroup.Key,
                Count = unitGroup.Count()
            };

        var queryResult = await AsyncExecuter.ToListAsync(query);

        // 机组与未确认报警记录数量的字典
        var unitUnAckedAlarmRecordCountDict = queryResult.ToLookup(q => q.UnitId, q => q.Count)
            .ToDictionary(group => group.Key, group => group.Sum());
            
        // Ams 开关量中机组开关量报警的数量
        var utcNow = _phmeeTimeProvider.GetUtcNow();

        List<RealTimeUnitsAlarmCountDto> result = new List<RealTimeUnitsAlarmCountDto>(units.Count);

        foreach (var unit in units)
        {
            // 振动 count
            int vibrationCount = unitUnAckedAlarmRecordCountDict.GetValueOrDefault(unit.Id, 0);

            // AMS 开关量 count
            var digitalConfigs =
                await _digitalConfigRepository.GetListAsync(x => x.VesselId == vesselId && x.UnitId == unit.Id);
            var tagNames = digitalConfigs.Select(x => x.TagName).ToList();
            var digitalRecords =
                await _digitalTDRepository.GetLatestRecordBeforeDateTimeByTagNamesAsync(vesselId, utcNow, tagNames);

            int amsDigitalCount = digitalRecords.Count(x => x.V == DigitalValue.Alarm);

            result.Add(new RealTimeUnitsAlarmCountDto
            {
                UnitId = unit.Id,
                UnitName = unit.DisplayName,
                UnitNumber = unit.Number,
                UnitType = unit.Type,
                AlarmCount = vibrationCount + amsDigitalCount
            });
        }

        return result;
    }

    // // 获取机组的设备参数
    // private List<EquipParametersDto> GetEquipParametersFromUnit(Unit unit)
    // {
    //     if (unit.Equipments == null || unit.Equipments.Count == 0)
    //     {
    //         return [];
    //     }
    //
    //     List<EquipParametersDto> equipParameters = [];
    //     foreach (var equipment in unit.Equipments)
    //     {
    //         var parameters = GetEquipParametersFromEquipment(equipment);
    //         equipParameters.Add(parameters);
    //     }
    //
    //     return equipParameters;
    // }

    // // 获取设备的设备参数
    // private EquipParametersDto GetEquipParametersFromEquipment(Equipment equipment)
    // {
    //     // 生产厂家
    //     string? manufacture = GetEquipParametersLocalizedName("Equipment", nameof(Equipment.Manufacturer));
    //
    //     Dictionary<string, string> equipAllParameters = new();
    //
    //     // 第一个参数固定为制造厂家        
    //     if (manufacture is not null)
    //     {
    //         equipAllParameters.Add(manufacture, equipment.Manufacturer ?? "");
    //     }
    //
    //     // 向 equipAllParameters 字典中设置其它设备属性
    //     SetOtherEquipParameters(equipment, equipAllParameters);
    //
    //     var equipParameters = new EquipParametersDto
    //     {
    //         EquipName = equipment.DisplayName,
    //         Parameters = equipAllParameters
    //     };
    //
    //     return equipParameters;
    // }

    /// <summary>
    /// 设置其它设备属性，获取本地化名称
    /// </summary>
    /// <param name="equipment"></param>
    /// <param name="parameters"></param>
    /// <exception cref="BusinessException"></exception>
    private void SetOtherEquipParameters(Equipment equipment, Dictionary<string, string> parameters)
    {
        string prefix = equipment.Type switch
        {
            EquipmentEnum.CentrifugalPump => nameof(CentrifugalPump),
            EquipmentEnum.AsynElectricMotor => nameof(AsynElectricMotor),
            // TODO: 抛出合适的异常
            _ => throw new BusinessException("Unknown equipment type")
        };

        // 其它设备属性
        foreach (var prop in equipment.DerivedPropertiesJson)
        {
            string? localizedName = GetEquipParametersLocalizedName(prefix, prop.Key);

            // 为防止页面中英文混杂，查不到则不到返回值中
            if (localizedName is null)
            {
                continue;
            }

            parameters.Add(localizedName, prop.Value?.ToString() ?? "");
        }
    }

    /// <summary>
    /// 根据 prefix:key 来从 _equipParametersLocalizer 中获取设备属性对应的本地化字符串
    /// </summary>
    /// <param name="prefix"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    private string? GetEquipParametersLocalizedName(string prefix, string key)
    {
        string resourceKey = $"{prefix}:{key}";
        string result = _equipParametersLocalizer[resourceKey];
        // 如果 result 为 `{prefix}:{key}`，则说明未找到对应的本地化字符串，则返回 null
        return string.Equals(result, resourceKey, StringComparison.OrdinalIgnoreCase)
            ? null
            : result;
    }
}